/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package stud.cp.user.dbaccess;

import java.math.BigInteger;
import java.sql.*;
import java.util.LinkedList;
import java.util.List;
import org.apache.log4j.Logger;
import stud.cp.core.dbaccess.DAO;
import stud.cp.core.dbaccess.DAOException;
import stud.cp.user.valueobjects.User;

/**
 * Класс, описывающий доступ к данным таблицы users
 *
 * @author Morfant
 */
public class UsersDAO implements DAO<User> {

    /**
     * Логгер.
     */
    private static Logger logger = Logger.getLogger(UsersDAO.class);
    /**
     * Список названий колонок таблицы users..
     */
    private static String[] usersColumnName = {"user_id",
        "user_type_id",
        "name",
        "password"};
    /**
     * Имя колонок таблицы property_types
     */
    private static String[] userTypesColumnName = {"user_type_id", "user_type_name"};
    /**
     * Массив с вспомогательными именами колонок.
     */
    private static String[] auxiliaryColumnName = {"count"};
    /**
     * Коннект к базе данных.
     */
    private Connection connection = null;

    /**
     * Конструктор, который устанавливает коннект к базе данных.
     *
     * @param connection Связь с базой данных.
     */
    public UsersDAO(Connection connection) {
        this.connection = connection;
    }

    /**
     * Метод, позволяющий получить пользователя по имени и паролю.
     *
     * @param name Имя
     * @param password Пароль.
     * @return Запрос
     */
    private String getQueryForGetByNameAndPassword(String name, String password) {
        StringBuilder query = new StringBuilder();

//        query.append("select u.user_id,u.user_type_id,u.name,u.password,ut.name as \"user_type_name\" \n");
//        query.append("from users u \n");
//        query.append("inner join user_types ut on ut.user_type_id=u.user_type_id \n");
//        query.append("where u.name='");
//        query.append(name);
//        query.append("' \n and password='");
//        query.append(password);
//        query.append("'");

        query.append("select name_param.object_id as user_id,"
                + "lv.list_value_id as user_type_id,"
                + "name_param.value as name,"
                + "pass_param.value as password,"
                + "lv.value as user_type_name\n");

        query.append("from parameters name_param\n");
        query.append("inner join parameters pass_param on name_param.object_id=pass_param.object_id\n");
        query.append("inner join parameters user_type_param on user_type_param.object_id=pass_param.object_id\n");
        query.append("inner join list_values lv on lv.list_value_id=user_type_param.list_value_id\n");
        query.append("where name_param.property_id=1\n");
        query.append("and pass_param.property_id=2\n");
        query.append("and user_type_param.property_id=3\n");
        query.append("and name_param.value='");
        query.append(name);
        query.append("'\n");
        query.append("and pass_param.value='");
        query.append(password);
        query.append("'");

        return query.toString();
    }

    /**
     * Метод, позволяющий получить пользователя по имени и паролю.
     *
     * @param name Имя
     * @param password Пароль
     * @return Пользователь.
     * @throws DAOException Ошибка достука к данным.
     */
    public User getByNameAndPassword(String name, String password) throws DAOException {
        User user = null;
        ResultSet set = null;
        PreparedStatement prestat = null;
        try {
            prestat = connection.prepareStatement(getQueryForGetByNameAndPassword(name, password));
            set = prestat.executeQuery();
            if (set.next()) {
                user =
                        new User(set.getBigDecimal(usersColumnName[0]).toBigInteger(),
                        set.getBigDecimal(usersColumnName[1]).toBigInteger(),
                        set.getString(usersColumnName[2]),
                        set.getString(usersColumnName[3]),
                        set.getString(userTypesColumnName[1]));
//                        "");//TODO::[High]Восстановить прежнюю логику, либо переписать старую.
            }
        } catch (SQLException ex) {
            logger.debug("DAO_propety", ex);
            throw new DAOException("Ошибка при создание данных..", ex);
        } finally {
            if (prestat != null) {
                try {
                    prestat.close();
                } catch (SQLException ex) {
                    logger.debug("DAO_propety", ex);
                    throw new DAOException("Ошибка закрытия ResultSet`а.", ex);
                }
            }
        }
        return user;
    }

    /**
     * Запрос, позволяющий получить запись по id.
     *
     * @param id ID
     * @return Запрос.
     */
    private String getQueryForGetById(BigInteger id) {
        StringBuilder query = new StringBuilder();

//        query.append("select u.user_id,u.user_type_id,u.name,u.password,ut.name as \"user_type_name\" \n");
//        query.append("from users u \n");
//        query.append("inner join user_types ut on ut.user_type_id=u.user_type_id \n");
//        query.append("where u.user_id=");
//        query.append(id);

        query.append("select obj.object_id as user_id,"
                + "lv.list_value_id as user_type_id,"
                + "name_param.value as name,"
                + "pass_param.value as password,"
                + "lv.value as user_type_name\n");

        query.append("from objects obj\n");
        query.append("inner join parameters name_param on name_param.object_id=obj.object_id\n");
        query.append("inner join parameters pass_param on name_param.object_id=pass_param.object_id\n");
        query.append("inner join parameters user_type_param on user_type_param.object_id=pass_param.object_id\n");
        query.append("inner join list_values lv on lv.list_value_id=user_type_param.list_value_id\n");
        query.append("where name_param.property_id=1\n");
        query.append("and pass_param.property_id=2\n");
        query.append("and user_type_param.property_id=3\n");
        query.append("and obj.object_id=");
        query.append(id);

        return query.toString();
    }

    /**
     * Метод, позволяющий получить запись по её ID
     *
     * @param id ID
     * @return Данные записи.
     * @throws DAOException <p> Ошикба доступа к данным. </p>
     */
    @Override
    public User getById(BigInteger id) throws DAOException {
        User user = null;
        ResultSet set = null;
        PreparedStatement prestat = null;
        try {
            prestat = connection.prepareStatement(getQueryForGetById(id));
            set = prestat.executeQuery();
            if (set.next()) {
                user =
                        new User(set.getBigDecimal(usersColumnName[0]).toBigInteger(),
                        set.getBigDecimal(usersColumnName[1]).toBigInteger(),
                        set.getString(usersColumnName[2]),
                        set.getString(usersColumnName[3]),
                        set.getString(userTypesColumnName[1]));
//                        "");//TODO::[High]Восстановить прежнюю логику, либо переписать старую.
            }
        } catch (SQLException ex) {
            logger.debug("DAO_propety", ex);
            throw new DAOException("Ошибка при создание данных..", ex);
        } finally {
            if (prestat != null) {
                try {
                    prestat.close();
                } catch (SQLException ex) {
                    logger.debug("DAO_propety", ex);
                    throw new DAOException("Ошибка закрытия ResultSet`а.", ex);
                }
            }
        }
        return user;
    }

    /**
     * Запрос на обновление данных.
     *
     * @param user_id ID пользователя.
     * @param user_type_id ID типа пользователя.
     * @param name Имя.
     * @param password Пароль.
     * @return Запрос.
     */
    private String getQueryForUpdate(BigInteger user_id, BigInteger user_type_id,
            String name, String password) {
        StringBuilder query = new StringBuilder();
        query.append("UPDATE users \n");
        query.append("SET user_type_id=");
        query.append(user_type_id);
        query.append(", \n");
        query.append("name='");
        query.append(name);
        query.append("', \n");
        query.append("password='");
        query.append(password);
        query.append("' \n");
        query.append("WHERE user_id=");
        query.append(user_id);
        return query.toString();
    }

    /**
     * Метод, позволяющий обновить данные.
     *
     * @param entry Запись.
     * @throws DAOException <p> Ошибка обновления данных. </p>
     */
    @Override
    public void update(User entry) throws DAOException {
        PreparedStatement prestat = null;
        try {
            prestat = connection.prepareStatement(
                    getQueryForUpdate(entry.getUserID(),
                    entry.getUserTypeID(),
                    entry.getName(),
                    entry.getPassword()));
            prestat.executeQuery();
        } catch (SQLException ex) {
            logger.debug("DAO_propeties", ex);
            throw new DAOException("Ошибка обновления данных.", ex);
        } finally {
            if (prestat != null) {
                try {
                    prestat.close();
                } catch (SQLException ex) {
                    logger.debug("DAO_propety", ex);
                    throw new DAOException("Ошибка закрытия ResultSet`а.", ex);
                }
            }
        }
    }

    /**
     * Запрос на добавление данных.
     *
     * @param user_id ID пользователя.
     * @param user_type_id ID типа пользователя.
     * @param name Имя.
     * @param password Пароль.
     * @return Запрос.
     */
    private String getQueryForInsert(BigInteger user_id,
            BigInteger user_type_id, String name,
            String password) {
        StringBuilder query = new StringBuilder();
        query.append("insert into users(user_id,user_type_id,name,password) \n");
        query.append("values(");
        query.append(user_id);
        query.append(",");
        query.append(user_type_id);
        query.append(",'");
        query.append(name);
        query.append("','");
        query.append(password);
        query.append("')");
        return query.toString();
    }

    /**
     * Зарпос на получения следующего ID.
     *
     * @return Зарпос.
     */
    private String getQueryForSelectNextID() {
        StringBuilder query = new StringBuilder();
        query.append("SELECT users_seq.NEXTVAL from dual \n");
        return query.toString();
    }

    /**
     * Зарпос на получения максимального id из таблицы.
     *
     * @return Зарпос.
     */
    private String getQueryForSelectMaxId() {
        StringBuilder query = new StringBuilder();
        query.append("SELECT MAX(user_id) \n");
        query.append("FROM users");
        return query.toString();
    }

    /**
     * Создание новой записи..
     *
     * @param entry Запись.
     * @throws DAOException <p> Ошибка сохранения данных. </p>
     */
    @Override
    public BigInteger create(User entry) throws DAOException {
        ResultSet set = null;
        PreparedStatement prestat = null;
        CallableStatement proc;
        try {
            proc = connection.prepareCall("{ call GetNextIdentity(?,?)}");
            proc.setString(1, "objects");
            proc.registerOutParameter(2, java.sql.Types.BIGINT);

            proc.execute();

            BigInteger index = proc.getBigDecimal(2).toBigInteger();

            proc.close();
            prestat = connection.prepareStatement(
                    getQueryForInsert(index,
                    entry.getUserTypeID(),
                    entry.getName(),
                    entry.getPassword()));
            prestat.executeUpdate();
            return index;
        } catch (SQLException ex) {
            logger.debug("DAO_propeties", ex);
            throw new DAOException("Ошибка при создание данных..", ex);
        } finally {
            if (prestat != null) {
                try {
                    prestat.close();
                } catch (SQLException ex) {
                    logger.debug("DAO_propety", ex);
                    throw new DAOException("Ошибка закрытия ResultSet`а.", ex);
                }
            }
        }
    }

    /**
     * Запрос, позволяющий при выполнении получить полностью данные из таблицы
     *
     * @return Строка запроса.
     */
    private String getQueryForAllSystemUser() {
        StringBuilder query = new StringBuilder();
//        query.append("select u.user_id,u.user_type_id,u.name,u.password,ut.name as \"user_type_name\" \n ");
//        query.append("from users u \n");
//        query.append("inner join user_types ut on ut.user_type_id=u.user_type_id \n");

        query.append("select obj.object_id as user_id,"
                + "lv.list_value_id as user_type_id,"
                + "name_param.value as name,"
                + "pass_param.value as password,"
                + "lv.value as user_type_name\n");

        query.append("from objects obj\n");
        query.append("inner join parameters name_param on name_param.object_id=obj.object_id\n");
        query.append("inner join parameters pass_param on name_param.object_id=pass_param.object_id\n");
        query.append("inner join parameters user_type_param on user_type_param.object_id=pass_param.object_id\n");
        query.append("inner join list_values lv on lv.list_value_id=user_type_param.list_value_id\n");
        query.append("where name_param.property_id=1\n");
        query.append("and pass_param.property_id=2\n");
        query.append("and user_type_param.property_id=3\n");

        return query.toString();
    }

    /**
     * Метод, который возвращает список значений из таблицы
     *
     * @return Список данных.
     */
    @Override
    public List<User> getAll() throws DAOException {
        List<User> data_list = new LinkedList<User>();
        ResultSet set = null;
        PreparedStatement prestat = null;
        try {
            prestat = connection.prepareStatement(getQueryForAllSystemUser());
            set = prestat.executeQuery();
            while (set.next()) {
                data_list.add(new User(set.getBigDecimal(usersColumnName[0]).toBigInteger(),
                        set.getBigDecimal(usersColumnName[1]).toBigInteger(),
                        set.getString(usersColumnName[2]),
                        set.getString(usersColumnName[3]),
                        set.getString(userTypesColumnName[1])));
//                        ""));//TODO::[High]
            }
        } catch (SQLException ex) {
            logger.debug("DAO_propeties", ex);
            throw new DAOException("Ошибка получения списка данных.", ex);
        } finally {
            if (prestat != null) {
                try {
                    prestat.close();
                } catch (SQLException ex) {
                    logger.debug("DAO_propety", ex);
                    throw new DAOException("Ошибка закрытия ResultSet`а.", ex);
                }
            }
        }
        return data_list;
    }

    private String getQueryForCheckIsUserAdmin(BigInteger userId) {
        StringBuilder query = new StringBuilder();
        query.append("select COUNT(*) as count\n ");
        query.append("from objects obj \n");
        query.append("inner join parameters param on param.object_id=obj.object_id\n");
        query.append("where param.property_id=3\n");
        query.append("and param.list_value_id=1\n");
        query.append("and obj.object_id = \n");
        query.append(userId);
        return query.toString();
    }

    /**
     * Метод, который возвращает всех администраторов.
     *
     * @return Список данных.
     */
    public boolean isUserAdmin(BigInteger userId) throws DAOException {
        boolean isAdmin = false;
        ResultSet set = null;
        PreparedStatement prestat = null;
        try {
            prestat = connection.prepareStatement(getQueryForCheckIsUserAdmin(userId));
            set = prestat.executeQuery();
            set.next();
            isAdmin = set.getLong(auxiliaryColumnName[0]) == 1 ? true : false;
        } catch (SQLException ex) {
            logger.debug("DAO_propeties", ex);
            throw new DAOException("Ошибка получения данных.", ex);
        } finally {
            if (prestat != null) {
                try {
                    prestat.close();
                } catch (SQLException ex) {
                    logger.debug("DAO_propety", ex);
                    throw new DAOException("Ошибка закрытия ResultSet`а.", ex);
                }
            }
        }
        return isAdmin;
    }

    /**
     * Запрос, позволяющий при выполнении получить полностью данные из таблицы
     *
     * @return Строка запроса.
     */
    private String getQueryForAdminCount() {
        StringBuilder query = new StringBuilder();
        query.append("select COUNT(*) as count\n ");
        query.append("from objects obj \n");
        query.append("inner join parameters param on param.object_id=obj.object_id\n");
        query.append("where param.property_id=3\n");
        query.append("and param.list_value_id=1");
        return query.toString();
    }

    /**
     * Метод, который возвращает всех администраторов.
     *
     * @return Список данных.
     */
    public long getAdminCount() throws DAOException {
        long count = 0;
        ResultSet set = null;
        PreparedStatement prestat = null;
        try {
            prestat = connection.prepareStatement(getQueryForAdminCount());
            set = prestat.executeQuery();
            set.next();
            count = set.getLong(auxiliaryColumnName[0]);
        } catch (SQLException ex) {
            logger.debug("DAO_propeties", ex);
            throw new DAOException("Ошибка получения данных.", ex);
        } finally {
            if (prestat != null) {
                try {
                    prestat.close();
                } catch (SQLException ex) {
                    logger.debug("DAO_propety", ex);
                    throw new DAOException("Ошибка закрытия ResultSet`а.", ex);
                }
            }
        }
        return count;
    }

    /**
     * Зарпос на удаление из таблицы записи по id.
     *
     * @param id ID
     * @return Зарпос.
     */
    private String getQueryForDelete(BigInteger id) {
        StringBuilder query = new StringBuilder();
        query.append("DELETE FROM users \n");
        query.append("WHERE user_id=");
        query.append(id);
        return query.toString();
    }

    /**
     * Удаление истории пользователя.
     *
     * @param id ID пользователя.
     * @return Запрос.
     */
    private String getQueryForDeleteUserHistory(BigInteger id) {
        StringBuilder query = new StringBuilder();
        query.append("DELETE FROM history \n");
        query.append("WHERE user_id=");
        query.append(id);
        return query.toString();
    }

    /**
     * Метод, позволяющий удалять данные по записи.
     *
     * @param id Запись.
     * @throws DAOException
     */
    @Override
    public void delete(BigInteger id) throws DAOException {
        PreparedStatement prestat = null;
        try {
            prestat = connection.prepareStatement(getQueryForDeleteUserHistory(id));
            prestat.executeUpdate();
            prestat.close();
            prestat = connection.prepareStatement(getQueryForDelete(id));
            prestat.executeUpdate();
        } catch (SQLException ex) {
            logger.debug("DAO_propeties", ex);
            throw new DAOException("Ошибка удаления данных.", ex);
        } finally {
            if (prestat != null) {
                try {
                    prestat.close();
                } catch (SQLException ex) {
                    logger.debug("DAO_propety", ex);
                    throw new DAOException("Ошибка закрытия ResultSet`а.", ex);
                }
            }
        }
    }
}
